/**
 * @file message.c
 *
 * @author cjeong
 */
#include <stdio.h>       /* printf */
#include <stdlib.h>      /* malloc */
#include <string.h>      /* strcpy, strdup */
#include <strings.h>     /* bzero */
#include <assert.h>      /* assert */
#include <jsmn.h>        /* jsmn_parse */
#include "message.h"


/*
 * Functions for message parsing
 *
 * STEP #1: _message_parse
 *   FROM: JSON message (char *)
 *     {
 *       "id"  : 1234567,
 *       "src" : "zmq://plato:5995",
 *       "tag" : "REACT_ADD",
 *       "arg" : {
 *         "name" : "batteryLowReactor",
 *         "code" : "REACTOR BEGIN; WATCH 102938; ... REACTOR END"
 *       }
 *     }
 *   TO: mvrt_value_t, where JSON message is represented as a map of 
 *       (mvrt_string_t, mvrt_value_t) pairs
 *
 * STEP #2: _message_build
 *   FROM: mvrt_value_t
 *   TO: mvrt_message_t
 */
typedef struct _parsedata {
  char *msg;         /* JSON message */
  int len;           /* JSON message length */
  jsmntok_t *toks;   /* array of tokens generated by JSMN */
  jsmntok_t *ctok;   /* current token */
  
} _parsedata_t;

static mvrt_message_t *_message_new();
static mvrt_value_t _message_parse(char *s);
static mvrt_value_t _parse_token(_parsedata_t *data);
static mvrt_value_t _parse_token_object(_parsedata_t *data);
static mvrt_value_t _parse_token_array(_parsedata_t *data);
static mvrt_value_t _parse_token_string(_parsedata_t *data);
static mvrt_value_t _parse_token_primitive(_parsedata_t *data);
static int _parse_tokenize(char *s, _parsedata_t *data);
static mvrt_message_t *_message_build(mvrt_value_t value);
static int _message_get_tag(char *tag_s);


static unsigned long long _mid = 0;
mvrt_message_t *_message_new()
{
  mvrt_message_t *msg = malloc(sizeof(mvrt_message_t));
  if (!msg)
    return NULL;
  
  msg->tag = MVRT_MESSAGE_INVALID;
  msg->arg = mvrt_value_null();

  msg->srcdev = NULL;
  msg->srcaddr = NULL;
  msg->srcid = 0;

  return msg;
}

mvrt_value_t _message_parse(char *s) 
{
  _parsedata_t data;
  int toksz;

  if (_parse_tokenize(s, &data) == -1) {
    printf("tokenize failure\n");
    return 0;
  }

  return _parse_token(&data);
}

int _parse_tokenize(char *s, _parsedata_t *data)
{
  jsmn_parser parser;           /* parser */
  unsigned int maxtokens;       /* max number of tokens */
  int ret;                      /* return value from jsmn_parse */

  jsmn_init(&parser);

  maxtokens = 64;
  data->msg = s;
  data->len = strlen(s);
  data->toks = malloc(sizeof(jsmntok_t) * maxtokens);
  do {
    maxtokens = maxtokens * 2 + 1;
    if (maxtokens > (1 << 16))
      break;

    data->toks = realloc(data->toks, sizeof(jsmntok_t) * maxtokens);
    ret = jsmn_parse(&parser, s, strlen(s), data->toks, maxtokens);
  } while (ret == JSMN_ERROR_NOMEM);

  switch (ret) {
  case JSMN_ERROR_INVAL: /* bad token, JSON string is corrupted */
  case JSMN_ERROR_NOMEM: /* not enough tokens, JSON string is too large */
  case JSMN_ERROR_PART:  /* JSON string too short, expecting more JSON data */
    return -1;
  default:
    break;
  }

  data->ctok = data->toks;

  return 0;
}

mvrt_value_t _parse_token(_parsedata_t *data)
{
  mvrt_value_t retval = 0;

#if 0
  char tokstr[1024];
  int toksz;
  int tokint;
  jsmntok_t *tok;
  tok = data->ctok;
  toksz = tok->end - tok->start;
  strncpy(tokstr, data->msg + tok->start, toksz);
  tokstr[toksz] = '\0';
  printf("_parse_token[%d:%d]: %s\n", tok->start, tok->end, tokstr);
#endif

  switch (data->ctok->type) {
  case JSMN_PRIMITIVE:
    retval = _parse_token_primitive(data);
    break;
  case JSMN_STRING:
    retval = _parse_token_string(data);
    break;
  case JSMN_ARRAY:
    retval = _parse_token_array(data);
    break;
  case JSMN_OBJECT:
    retval = _parse_token_object(data);
    break;
  default:
    assert(0);
  }

#if 0
  mvrt_value_print(retval);
#endif

  return retval;
}

mvrt_value_t _parse_token_primitive(_parsedata_t *data)
{
  jsmntok_t *tok;       /* token */
  char tokstr[1024];    /* token string */
  int toksz;            /* token length */
  int tokint;           /* integer */

  tok = data->ctok;
  toksz = tok->end - tok->start;
  if (toksz <= 0)
    return 0;
  assert(toksz <= 1023);
    
  strncpy(tokstr, data->msg + tok->start, toksz);
  tokstr[toksz] = '\0';

  /* TODO: for now, only support integers */
  sscanf(tokstr, "%d", &tokint);
  mvrt_value_t value = mvrt_value_int(tokint);

  return value;
}

mvrt_value_t _parse_token_string(_parsedata_t *data)
{
  jsmntok_t *tok;       /* token */
  char tokstr[1024];    /* token string */
  int toksz;            /* token length */
  
  tok = data->ctok;
  toksz = tok->end - tok->start;
  if (toksz <= 0)
    return 0;
  assert(toksz <= 1023);
  
  strncpy(tokstr, data->msg + tok->start, toksz);
  tokstr[toksz] = '\0';

  mvrt_value_t value = mvrt_value_string(tokstr);
  return value;
}

mvrt_value_t _parse_token_object(_parsedata_t *data)
{
  mvrt_value_t map;      /* map */
  mvrt_value_t key;      /* key */
  mvrt_value_t value;    /* value */

  map = mvrt_value_map();

  data->ctok++;
  while (data->ctok && (data->ctok->start < data->ctok->end)) {
    if ((key = _parse_token(data)) == 0)
      return map;

    data->ctok++;
    if ((value = _parse_token(data)) == 0)
      return map;

    if (mvrt_value_map_add(map, key, value) == mvrt_value_null()) {
      assert(0);
      return 0;
    }
    /*
      fprintf(stdout, "\t mapinsert: ");
      mv_value_print(key);
      mv_value_print(value);
    */
    data->ctok++;
  } 

  return map;
}

mvrt_value_t _parse_token_array(_parsedata_t *data)
{
  mvrt_value_t cons;    /* cons */
  mvrt_value_t prev;    /* cons */
  mvrt_value_t value;   /* array element */

  prev = mvrt_value_null();
  cons = prev;
  data->ctok++;
  while (data->ctok && (data->ctok->start < data->ctok->end)) {
    if ((value = _parse_token(data)) == 0)
      return cons;

    cons = mvrt_value_cons();
    mvrt_value_cons_setcar(cons, value);
    mvrt_value_cons_setcdr(cons, prev);
    prev = cons;
    data->ctok++;
  }

  return cons;
}

static const char *_tag_strs[] = {
  "INVALID",

  "EVENT_ADD",
  "EVENT_DEL",
  "EVENT_CHK",

  "EVENT_SUB",
  "EVENT_UNSUB",
  "EVENT_OCCUR",

  /* properties */
  "PROP_ADD",
  "PROP_DEL",
  "PROP_CHK",

  "PROP_GET",
  "PROP_SET",
  "PROP_RET",
  
  /* funtions */
  "FUNC_ADD",
  "FUNC_DEL",
  "FUNC_CHK",

  "FUNC_CALL",
  "FUNC_CALL_NORET",

  /* reactors */
  "REACT_ADD",
  "REACT_DEL",
  "REACT_CHK",

  /* CONTROL */
  "REPLY"

  ""
};

const char *_encode_tag(int tag)
{
  if (tag > MVRT_MESSAGE_NTAGS || tag < 0) 
    return NULL;
  
  return _tag_strs[tag];
}

int _message_get_tag(char *tag_s)
{
  /* TODO: make this more efficient */
  int i;
  for (i = 0; i < MVRT_MESSAGE_NTAGS; i++) {
    if (!strcmp(tag_s, _tag_strs[i]))
      return i;
  }

  return -1;
}

mvrt_message_t *_message_build(mvrt_value_t value)
{
  mvrt_value_t tagkey = mvrt_value_string("tag");
  mvrt_value_t tagval = mvrt_value_map_lookup(value, tagkey);
  if (mvrt_value_is_null(tagval))
    return NULL;

  printf("TAG:");
  mvrt_value_print(tagval);

  mvrt_value_t argkey = mvrt_value_string("arg");
  mvrt_value_t argval = mvrt_value_map_lookup(value, argkey);
  if (mvrt_value_is_null(argval))
    return NULL;

  printf("ARG:");
  mvrt_value_print(argval);
  
  mvrt_message_t *msg = _message_new();
  msg->tag = _message_get_tag(mvrt_value_string_get(tagval));
  msg->arg = argval;

  return msg;
}


/*
 * Functions for the message API.
 */
mvrt_message_t *mvrt_message_parse(char *s)
{
  mvrt_value_t value = _message_parse(s);
  if (mvrt_value_is_null(value))
    return NULL;

  mvrt_value_print(value);

  return _message_build(value);
}

mvrt_message_t *mvrt_message_new()
{
  return _message_new();
}

void mvrt_message_delete(mvrt_message_t *msg)
{
  if (!msg)
    return;

  free(msg);
}

